作者:鄙人fisher_779 | 来源:互联网 | 2023-10-11 14:19
篇首语:本文由编程笔记#小编为大家整理,主要介绍了UE4Unlua源码解析7-Lua通过UE命名空间访问C++类型的实现原理相关的知识,希望对你有一定的参考价值。
篇首语:本文由编程笔记#小编为大家整理,主要介绍了UE4 Unlua源码解析7 - Lua通过UE命名空间访问C++类型的实现原理相关的知识,希望对你有一定的参考价值。
Lua通过UE命名空间访问C++类型的实现原理
- 1.1 UE4发生了 什么
- 1.2 UE4.UKismetSystemLibrary发生了什么
- 1.3 UE4.UKismetSystemLibrary.PrintString发生了什么
- 1.4 UE4.UKismetSystemLibrary.PrintString(“Hello”)发生了什么
举例 UE4.UKismetSystemLibrary.PrintString(“hello”)
我们来看Unlua提供的例子的HelloWorld
代码是
local hello = “HelloWorld”
UE4.UKismetSystemLibrary.PrintString(hello)
这个例子我们最终会调到UE4的方法,并且成功执行,那么首先,我们要解答的问题就是为什么Lua这么写最终能调用到C++的方法,
而且Lua传的参数是Lua的字符串,为什么C++能成功执行,这简单的代码,背后到底做了什么事情
1.1 UE4发生了 什么
Unlua.lua中声明了UE4,可以看到UE4其实就是全局表_G,而且他的元表是global_mt,Index元方法为global_index,我们去看看global_index
第73行拿到传入的索引k,检查第一个字母是不是UE的类的首字母,是就调用RegisterClass,如果是E,就调用RegisterEnum,然后调用rawget,rawget方法是不使用Index元方法的取值函数,可以猜测上面的函数执行完之后,t表也就是UE4表里已经就有了索引k的值了。
具体的执行逻辑接着看
1.2 UE4.UKismetSystemLibrary发生了什么
由上节讲到的,UE4.UKismetSystemLibrary的时候,因为UE4是表,所以就去表里找UKismetSystemLibrary,但是第一次肯定是找不到的,于是走到UE4的元表的元方法Index,也就是执行global_index,传进来的t是UE4,k是“UKismetSystemLibrary”
由之前的分析可以看到代码会走到RegisterClass,然后传入k
RegisterClass在lua测的声明如下
看到LuaContext.cpp里
lua api lua_register前面讲到了,将C函数f设置为全局名称name的新值,lua端可以通过name调用C方法f
所以这行代码将C函数Global_RegisterClass注册,在Lua端通过Lua全局名称RegisterClass调用
Global_RegisterClass和RegisterClass逐行解释之前讲过了,此处只说结果
Global_RegisterClass 读虚拟栈的大小,当小于1的时候返回,否则调用RegisterClass,参数传(虚拟机,栈底的值转成C 的string)
此时虚拟栈的内容是传进来类名“UKismetSystemLibrary”,以及参数的数量1,所以传递给RegisterClass的参数是(虚拟机L,“UKismetSystemLibrary”)
RegisterClass逐行解释之前讲过了,总之是两大部分
1 根据UE4反射生成UClass的描述信息FClassDesc ,然后存储到UnLua的存储全局反射数据的GReflectionRegistry对象中。
2 根据生成的FClassDesc 为其在Lua中注册table,然后设置table的元表为自己,然后往table里塞一些元方法
RegisterClass结束后,UE4.UKismetSystemLibrary就是一个Lua端的表了,这个表的元表是自己,并且元方法Index是LuaCore中的方法Class_Index
类图如下
1.3 UE4.UKismetSystemLibrary.PrintString发生了什么
截至目前,UE4.UKismetSystemLibrary就能拿到上一步生成的Lua table了,此时调用.PrintString的时候,可以看到表里没用这个数据,所以走到元表的Index元方法,之前我们知道,元方法为LuaCore中的Class_Index,所以重点看这个方法即可
其中最重要的是走到GetField
GetField和RegiestClass很像,就像是注册类一样,GetField主要也做两件事
1
如果传进来的是属性,就根据UE4反射生成FPropertyDesc
如果传进来的是方法,就根据UE4反射生成FFunctionDesc,并且把FPropertyDesc或FFunctionDesc存在FClassDesc中
2
如果是属性 就在表里存FPropertyDesc,然后将FPropertyDesc压栈,作为返回值返回
如果是方法,就在表里存FFunctionDesc+C方法Class_CallUFunction的闭包进去,FFunctionDesc作为Upvalue。然后将FFunctionDesc+C方法Class_CallUFunction的闭包压栈,作为返回值返回
此时的类图如下
1.4 UE4.UKismetSystemLibrary.PrintString(“Hello”)发生了什么
截至目前,相当于调用LuaCore的Class_CallUFunction
传进来的参数是lua的string hello
根据之前的解释C++通过闭包里的FFunctionDesc执行方法CallUE,参数是Lua传进去的,通过
1)执行PreCall,根据【UFunctionDesc】和Lua参数,将Lua参数转换成C++参数,再根据【UFunctionDesc】反射信息,将C++参数写入到一个缓存区中
2)执行 UObject::ProcessEvent(FinalFunction, Params),参数FinalFunction是UFunction,参数Params是前面保存有C++参数的缓存区。ProcessEvent执行时,即调用了真正我们想调用的C++ Create函数,然后把C++返回值放入了Params缓存区中
3)执行PostCall,从Params缓存区中读出C++返回值,转换成Lua返回值,Push进Lua栈,返回给Lua。
结束,至此,屏幕应该输出Hello